#pragma once

#include "common.hpp"
#include <iostream>
#include <string>
#include <vector>

struct Data {
private:
    std::vector<uint8_t> data;

public:
    Data() = default;

    Data(uint8_t *arr, uint16_t len);

    Data(uint32_t value);

    Data(uint8_t value);

    Data(const std::initializer_list<uint8_t> &list);

    Data(uint16_t *arr, uint16_t len);

    Data(std::vector<uint8_t>::const_iterator first, std::vector<uint8_t>::const_iterator second);

    Data(const std::vector<uint8_t> &d);

    Data &operator=(uint32_t val);

    Data &operator=(const Data &v);

    bool operator==(const Data &rData) const;

    bool operator!=(const Data &d) const;

    bool empty(void) const;

    template <typename T>
    T get() const;

    template <typename T>
    uint8_t getBit(uint8_t bit) const;

    template <typename T>
    Data getPartBits(int start, int end) const;

    Data subPartByte(int start, int end) const;

    const std::vector<uint8_t> &rawData(void) const;

    std::size_t size(void) const;

    std::string toHexString(void) const;

    std::string toMacString(void) const;
};

Data::Data(uint8_t *arr, uint16_t len)
{
    data.insert(data.end(), arr, arr + len);
}

Data::Data(uint8_t value)
{
    data.emplace_back(value);
}

Data::Data(uint32_t value)
{
    data.push_back(value >> 24 & 0xff);
    data.push_back(value >> 16 & 0xff);
    data.push_back(value >> 8 & 0xff);
    data.push_back(value & 0xff);
}

Data::Data(const std::initializer_list<uint8_t> &list)
{
    data = list;
}

Data::Data(uint16_t *arr, uint16_t len)
{
    for (int i = 0; i < len; i++) {
        data.insert(data.end(), {U8(arr[i] >> 8 & 0xff), U8(arr[i])});
    }
}

Data::Data(std::vector<uint8_t>::const_iterator first, std::vector<uint8_t>::const_iterator second)
{
    data = std::vector<uint8_t>(first, second);
}

Data::Data(const std::vector<uint8_t> &d)
{
    data = std::move(d);
}

Data &Data::operator=(uint32_t val)
{
    data = {U8(val >> 24), U8(val >> 16), U8(val >> 8), U8(val)};
    return *this;
}

Data &Data::operator=(const Data &v)
{
    data = v.data;
    return *this;
}

bool Data::operator==(const Data &rData) const
{
    return data == rData.data;
}

bool Data::operator!=(const Data &d) const
{
    return data != d.data;
}

bool Data::empty(void) const
{
    return data.empty();
}

template <typename T>
T Data::get() const
{
    auto len = std::min(sizeof(T), data.size());

    T temp {0};

    for (int i = 0; i < len; i++) {
        temp |= data[i];
        if (i != len - 1) {
            temp <<= 8;
        }
    }

    return temp;
}

template <>
std::string Data::get() const
{
    return std::string(data.begin(), data.end());
}

template <typename T>
uint8_t Data::getBit(uint8_t bit) const
{
    return get<T>() >> bit & 0x1;
}

template <typename T>
Data Data::getPartBits(int start, int end) const
{
    if (end < start) {
        return Data {};
    }

    if (start < 0 || end < 0) {
        return Data {};
    }

    if (end > data.size() * 8 - 1) {
        return Data {};
    }

    unsigned int mask = (1 << (end - start + 1)) - 1;
    mask <<= start;

    return Data((get<T>() & mask) >> start);
}

Data Data::subPartByte(int start, int end) const
{
    if (end < start) {
        return Data {};
    }

    if (start < 0 || end < 0) {
        return Data {};
    }

    if (end > data.size() - 1) {
        return Data {};
    }

    return Data(data.begin() + start, data.begin() + start + (end - start + 1));
}

const std::vector<uint8_t> &Data::rawData(void) const
{
    return data;
}

std::size_t Data::size(void) const
{
    return data.size();
}

std::string Data::toHexString(void) const
{
    std::stringstream ss;
    for (const auto &it : data) {
        ss << std::hex << std::setw(2) << std::setfill('0') << U32(it);
    }

    return ss.str();
}

std::string Data::toMacString(void) const
{
    std::stringstream ss;

    for (auto it = data.begin(); it != data.end(); it++) {
        ss << std::hex << std::setw(2) << std::setfill('0') << std::uppercase << U32(*it);
        if (std::next(it) != data.end()) {
            ss << ":";
        }
    }

    return ss.str();
}

using OldData_t = Data;
using NewData_t = Data;
